﻿namespace ClassyBlog.Infrastructure
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;

    using DataAccess;
    using DomainObjects;
    using Web;

    public class ServiceRegistrations
    {
        private readonly string dataDirectory;
        private readonly IServiceContainer container;

        public ServiceRegistrations(
            string dataDirectory, IServiceContainer container)
        {
            if (string.IsNullOrWhiteSpace(dataDirectory))
            {
                throw new ArgumentException(
                    TextMessages.ServiceRegistration_Ctor_Data_Directory_Bank,
                    "dataDirectory");
            }

            if (container == null)
            {
                throw new ArgumentNullException("container");
            }

            this.dataDirectory = dataDirectory;
            this.container = container;
        }

        public virtual void Register()
        {
            RegisterInfrastructuralServices();
            RegisterRepositories();
            RegisterFormatters();
            RegisterMacros();
            RegisterControllers();
            RegisterFilters();
            SetResolver();
        }

        private void RegisterInfrastructuralServices()
        {
            container.RegisterAsSingleton<IBuildManager>(c =>
                new DefaultBuildManager())
                     .RegisterAsSingleton<IFileSystem>(c => new FileSystem())
                     .RegisterAsSingleton<IHttp>(c => new Http())
                     .RegisterAsSingleton<IPathResolver>(c =>
                         new PathResolver(c.Resolve<IFileSystem>()))
                     .RegisterAsSingleton<IArticleFactory<Page>>(c =>
                         new PageFactory(c.Resolve<IFileSystem>()))
                     .RegisterAsSingleton<IArticleFactory<Post>>(c =>
                         new PostFactory(c.Resolve<IFileSystem>()))
                     .RegisterAsTransient<ICurrentBlogProvider>(c =>
                         new CurrentBlogProvider(c.Resolve<IBlogRepository>()))
                     .RegisterAsSingleton<IViewPageActivator>(c =>
                         new DefaultViewPageActivator())
                     .RegisterAsSingleton<IRenderer>(c =>
                         new Renderer(
                             c.Resolve<IEnumerable<IFormatter>>(),
                             c.Resolve<IMacroRunner>()))
                     .RegisterAsTransient<Func<HttpContextBase>>(c => 
                         () => new HttpContextWrapper(HttpContext.Current));
        }

        private void RegisterRepositories()
        {
            container.RegisterAsSingleton<IPageRepository>(c =>
                new PageRepository(
                    dataDirectory,
                    c.Resolve<IPathResolver>(),
                    c.Resolve<IArticleFactory<Page>>()))
                     .RegisterAsSingleton<IPostRepository>(c =>
                         new PostRepository(
                             dataDirectory,
                             c.Resolve<IPathResolver>(),
                             c.Resolve<IArticleFactory<Post>>()))
                     .RegisterAsSingleton<IBlogRepository>(c =>
                         new BlogRepository(
                             dataDirectory, c.Resolve<IFileSystem>()));
        }

        private void RegisterFormatters()
        {
            container.RegisterAsSingleton(c => new MarkdownFormatter())
                     .RegisterAsSingleton(c => new TextileFormatter())
                     .RegisterAsSingleton(c => new NullFormatter())
                     .RegisterAsSingleton<IEnumerable<IFormatter>>(c =>
                         new List<IFormatter>
                             {
                                 c.Resolve<MarkdownFormatter>(),
                                 c.Resolve<TextileFormatter>(),
                                 c.Resolve<NullFormatter>()
                             });
        }

        private void RegisterMacros()
        {
            Func<IServiceContainer, IEnumerable<object>> getMacros = c =>
            {
                var buildManager = c.Resolve<IBuildManager>();

                var macroTypes = buildManager.PrivateAssemblies
                                             .SelectMany(assembly =>
                                                 assembly.GetTypes())
                                             .Where(type =>
                                                 type.IsPublic &&
                                                 type.IsClass &&
                                                 !type.IsAbstract)
                                             .Where(type =>
                                                 type.GetCustomAttributes(
                                                 typeof(MacroAttribute),
                                                 false).Any())
                                             .Where(type =>
                                                 type.GetConstructors().
                                                 Any(ctor =>
                                                     !ctor.GetParameters()
                                                     .Any()));

                return macroTypes.Select(Activator.CreateInstance);
            };

            container.RegisterAsSingleton<IMacroRunner>(
                c => new MacroRunner(getMacros(c)));
        }

        private void RegisterControllers()
        {
            container.RegisterAsTransient(c =>
                        new PagesController(c.Resolve<IPageRepository>()))
                     .RegisterAsTransient(c =>
                         new PostsController(c.Resolve<IPostRepository>()))
                     .RegisterAsTransient(c =>
                         new ContentProxyController(c.Resolve<IHttp>()));
        }

        private void RegisterFilters()
        {
            container.RegisterAsTransient(c =>
                new CurrentBlog(c.Resolve<ICurrentBlogProvider>()));
        }

        private void SetResolver()
        {
            DependencyResolver.SetResolver(
                new DefaultDependencyResolver(container));
        }
    }
}